package ssau.knyazev.modules;

import java.util.Arrays;

import ssau.knyazev.common.UniException;

public class MatrixMath
{

	public static final int MULTIPLICATION_IJK = 0;
	public static final int MULTIPLICATION_WINOGRAD = 1;
	public static final int MULTIPLICATION_STRASSEN = 2;
	public static final int LU_ROWS = 3;
	public static final int LU_COLS = 4;
	
	public static float[][] multiplication(float[][] a, int x) throws UniException{
		return multiplication(a, (float)x);
	}
	
	public static float[][] multiplication(float[][] a, float x) throws UniException{
		float[][] c = new float[a.length][a[0].length];
		for (int i = 0; i < c.length; i++){
			for (int j = 0; j < c[0].length; j++){
				c[i][j] = a[i][j] * x;
			}
		}
		return c;
	}
	
	public static float[] multiplication(float[][] matrix, float[] vector){
		float[] res = new float[vector.length];
		for (int i = 0; i < res.length; i++){
			for (int j = 0; j < res.length; j++){
				res[j] += matrix[i][j]*vector[j];
			}
		}
		return res;
	}
	
	public static float[][] multiplication(float[][] a, float[][] b) throws UniException{
		return multiplication(a, b, MULTIPLICATION_IJK);
	}
	
	public static float[][] multiplication(float[][] a, float[][] b, int mode) throws UniException{
		if (mode == MULTIPLICATION_IJK){
			return multyIJK(a, b);
		} else if (mode == MULTIPLICATION_WINOGRAD){
			return multyWinograd(a, b);
		} else if (mode == MULTIPLICATION_STRASSEN){
			return multyStrassen(a, b);
		} else {
			throw new UniException("This mode is not supported: " + mode);
		}
	}
	
	protected static float[][] multyIJK(float[][] a, float[][] b) throws UniException{
		if (a[0].length != b.length){
			throw new UniException("Matrixes can not be multiplicated");
		}
		float[][] c = new float[a.length][b[0].length];
		for (int i = 0; i < c.length; i++){
			for (int j = 0; j < c[0].length; j++){
				for (int k = 0; k < a[0].length; k++){
					c[i][j] = c[i][j] + a[i][k] * b[k][j];
				}
			}
		}
		return c;
	}
	
	protected static float[][] multyWinograd(float[][] a, float[][] b) throws UniException{
		if (a[0].length != b.length){
			throw new UniException("Matrixes can not be multiplicated");
		}
		float[][] c = new float[a.length][b[0].length];
		
		float[] row = new float[a.length];
		for (int i = 0; i < a.length; i++){
			row[i] = a[i][0] * a[i][1];
			for (int k = 1; k < b.length/2; k++){
				row[i] = row[i] + a[i][2*k] * a[i][2*k+1];
			}
		}
		
		float[] col = new float[a.length];
		for (int j = 0; j < b[0].length; j++){
			col[j] = b[0][j] * b[1][j];
			for (int k = 1; k < b.length/2; k++){
				col[j] = col[j] + b[2*k][j] * b[2*k+1][j];
			}
		}
		
		for (int i = 0; i < c.length; i++){
			for (int j = 0; j < c[0].length; j++){
				c[i][j] = -row[i] - col[j];
				for (int k = 0; k < b[0].length/2; k++){
					c[i][j] = c[i][j] + (a[i][2*k+1] + b[2*k][j]) * (a[i][2*k] + b[2*k+1][j]);
				}
			}
		}
		return c;
	}

	protected static float[][] multyStrassen(float[][] a, float[][] b) throws UniException{
		if (a.length % 2 != 0 || b.length != 0 || b[0].length != 0){
			return multyIJK(a, b);
		}
		if (a[0].length != b.length){
			throw new UniException("Wrong size of matrixes");
		}
		float[][] c = new float[a.length][b[0].length];
		
		float[][] a11, a12, a21, a22;
		float[][] b11, b12, b21, b22;
		float[][] p1, p2, p3, p4, p5, p6, p7;
		
		a11 = submatrix(a, 0, 0, a.length/2-1, a[0].length/2-1);
		a12 = submatrix(a, 0, a[0].length/2, a.length/2-1, a[0].length-1);
		a21 = submatrix(a, a.length/2, 0, a.length-1, a[0].length/2-1);
		a22 = submatrix(a, a.length/2, a[0].length/2, a.length-1, a[0].length-1);
		
		b11 = submatrix(b, 0, 0, b.length/2-1, b[0].length/2-1);
		b12 = submatrix(b, 0, b[0].length/2, b.length/2-1, b[0].length-1);
		b21 = submatrix(b, b.length/2, 0, b.length-1, b[0].length/2-1);
		b22 = submatrix(b, b.length/2, b[0].length/2, b.length-1, b[0].length-1);
		
		p1 = multyIJK(addition(a11, a22), addition(b11, b22));
		p2 = multyIJK(addition(a21, a22), b11);
		p3 = multyIJK(a11, subtraction(b12, b22));
		p4 = multyIJK(a22, subtraction(b21, b11));
		p5 = multyIJK(addition(a11, a12), b22);
		p6 = multyIJK(subtraction(a21, a11), addition(b11, b12));
		p7 = multyIJK(subtraction(a12, a22), addition(b21, b22));
		
		float[][] tmp = addition(p1, p4);
		tmp = subtraction(tmp, p5);
		tmp = addition(tmp, p7);
		for (int i = 0; i < c.length/2; i++){
			for (int j = 0; j < c[0].length/2; j++){
				c[i][j] = tmp[i][j];
			}
		}
		
		tmp = addition(p3, p5);
		int div = c[0].length/2;
		for (int i = 0; i < c.length/2; i++){
			for (int j = c[0].length/2; j < c[0].length; j++){
				c[i][j] = tmp[i][j-div];
			}
		}
		
		tmp = addition(p2, p4);
		div = c.length/2;
		for (int i = c.length/2; i < c.length; i++){
			for (int j = 0; j < c[0].length/2; j++){
				c[i][j] = tmp[i-div][j];
			}
		}
		
		tmp = addition(p1, p3);
		tmp = subtraction(tmp, p2);
		tmp = addition(tmp, p6);
		for (int i = c.length/2; i < c.length; i++){
			for (int j = c[0].length/2; j < c[0].length; j++){
				c[i][j] = tmp[i-div][j-div];
			}
		}
		
		return c;
	}
	
	public static float[][] submatrix(float[][] src, int row0, int col0, int row1, int col1) throws UniException{
		try{
			float[][] res = new float[row1 - row0 + 1][col1 - col0 + 1];
			for (int i = 0; i < res.length; i++){
				for (int j = 0; j < res[0].length; j++){
					res[i][j] = src[i+row0][j+col0];
				}
			}
			return res;
		} catch (ArrayIndexOutOfBoundsException e){
			throw new UniException("Incorrect parameters of submatrix");
		}
	}
	
	public static float[][] copyMatrix(float[][] src){
		float[][] x = new float[src.length][src[0].length];
		for (int i = 0; i < src.length; i++){
			x[i] = Arrays.copyOf(src[i], src[i].length);
		}
		return x;
	}
	
	public static float[][] addition(float[][] a, float[][] b) throws UniException{
		if (a.length != b.length || a[0].length != b.length){
			throw new UniException("Size of matrixes should be equal");
		}
		float[][] c = new float[a.length][a[0].length];
		for (int i = 0; i < c.length; i++){
			for (int j = 0; j < c[i].length; j++){
				c[i][j] = a[i][j] + b[i][j];
			}
		}
		return c;
	}
	
	public static float[][] addition(float[][] a, int x) throws UniException{
		return addition(a, (float) x);
	}
	
	public static float[][] addition(float[][] a, float x) throws UniException{
		float[][] c = new float[a.length][a[0].length];
		for (int i = 0; i < c.length; i++){
			for (int j = 0; j < c[i].length; j++){
				c[i][j] = a[i][j] + x;
			}
		}
		return c;
	}
	
	public static float[][] subtraction(float[][] a, float[][] b) throws UniException{
		if (a.length != b.length || a[0].length != b.length){
			throw new UniException("Size of matrixes should be equal");
		}
		float[][] c = new float[a.length][a[0].length];
		for (int i = 0; i < c.length; i++){
			for (int j = 0; j < c[i].length; j++){
				c[i][j] = a[i][j] - b[i][j];
			}
		}
		return c;
	}
	
	public static float[][] subtraction(float[][] a, int x) throws UniException{
		return subtraction(a, (float) x);
	}
	
	public static float[][] subtraction(float[][] a, float x) throws UniException{
		float[][] c = new float[a.length][a[0].length];
		for (int i = 0; i < c.length; i++){
			for (int j = 0; j < c[i].length; j++){
				c[i][j] = a[i][j] - x;
			}
		}
		return c;
	}
	
	public static float mean(float[][] src){
		float res = 0;
		for (int i = 0; i < src.length; i++){
			for (int j = 0; j < src[i].length; j++){
				res += src[i][j];
			}
		}
		return res/(src.length*src.length);
	}
	
	public static float var(float[][] src){
		float res = 0;
		float middle = mean(src);
		for (int i = 0; i < src.length; i++){
			for (int j = 0; j < src[i].length; j++){
				res += (src[i][j] - middle)*(src[i][j] - middle);
			}
		}
		return res/(src.length*src.length);
	}
	
	public static float max(float[][] src){
		float res = src[0][0];
		for (int i = 0; i < src.length; i++){
			for (int j = 0; j < src[i].length; j++){
				res = (res < src[i][j]) ? src[i][j] : res;
			}
		}
		return res;
	}
	
	public static float min(float[][] src){
		float res = src[0][0];
		for (int i = 0; i < src.length; i++){
			for (int j = 0; j < src[i].length; j++){
				res = (res > src[i][j]) ? src[i][j] : res;
			}
		}
		return res;
	}
	
}
